package org.power.bus.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.csv.CsvRow;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.power.bus.dao.*;
import org.power.bus.entity.CountResponse;
import org.power.bus.model.*;
import org.power.bus.service.PowerPredictService;
import org.power.core.config.PowerConfig;
import org.power.core.exception.PowerException;
import org.power.core.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.power.core.consts.Constants;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;

@Service
public class PowerPredictServiceImpl implements PowerPredictService {
    private static final Logger LOGGER = LoggerFactory.getLogger(PowerServiceImpl.class);

    private static final String OS = System.getProperty("os.name").toLowerCase();

    @Resource
    private PowerConfig powerConfig;

    @Value("${power.model_file_path}")
    private static String modelFilePath = "/data/power/model";

    @Value("${power.python}")
    private static String python = "python";

    @Resource
    private EquipmentDao equipmentDao;

    @Resource
    private TemperatureDao temperatureDao;

    @Resource
    private CurrentDao currentDao;

    @Resource
    private UploadDao uploadDao;

    @Resource
    private PredictFeatureDao predictFeatureDao;

    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    private static final Map<String, Integer> equipmentMap = new HashMap<>();

    // 后期建议从数据库或redis中获取
    private static final Map<String, String> AVAILABLE_MODELS_MAP = new HashMap<>();

    private List<Map<String, String>> deviceNumberlList = new ArrayList<>();

    private List<Map<String, String>> deviceNamelList = new ArrayList<>();


    /**
     * 电流表名称到设备名称的映射
     * TODO升高座
     * 每个电流表对应的是A、B、C三相设备吗？
     */
    private static final Map<String, String> currentMap = new HashMap<>();

    static {

//        currentMap.put("#1主变35kV", "1号主变35KV套管接线夹");
//        currentMap.put("#1主变220kV", "1号主变220KV套管接线夹");
//        currentMap.put("#1主变500kV", "1号主变500KV套管接线夹");

        currentMap.put("#2主变35kV", "2号主变35KV套管接线夹");
//        currentMap.put("#2主变220kV", "2号主变220KV套管接线夹");
//        currentMap.put("#2主变500kV", "2号主变500KV套管接线夹");

//        currentMap.put("#1主变35kV", "1号主变35KV套管升高座");
//        currentMap.put("#1主变220kV", "1号主变220KV套管升高座");
//        currentMap.put("#1主变500kV", "1号主变500KV套管升高座");
//
//        currentMap.put("#2主变35kV", "2号主变35KV套管升高座");
//        currentMap.put("#2主变220kV", "2号主变220KV套管升高座");
//        currentMap.put("#2主变500kV", "2号主变500KV套管升高座");

        // 给模型文件赋值
        AVAILABLE_MODELS_MAP.put("LSTM", modelFilePath + File.separator + "LSTM.h5");
        AVAILABLE_MODELS_MAP.put("GRU", modelFilePath + File.separator + "GRU.h5");
    }



    public boolean mergePredictFeature() {
        // 清空特征表数据（防止重复数据）
        predictFeatureDao.truncateFeature();
        // 将电流表和温度表关联，合并得到特征表
        List<CountResponse> currentResponseList = currentDao.getEquipmentCurrentLast();
        for (CountResponse response : currentResponseList) {
            // 校验电流表与温度表是否有能够匹配的数据
            LambdaQueryWrapper<Temperature> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Temperature::getEquipmentName, response.getEquipmentName());
            queryWrapper.last(" and DATE_FORMAT(check_time, '%Y-%m-%d') = '" + response.getDate() + "'");
            List<Temperature> temperatureList = temperatureDao.selectList(queryWrapper);
            int temperatureCount = temperatureList.size();
            if (0 == temperatureCount) {
                continue;
            }
            LambdaQueryWrapper<Current> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(Current::getEquipmentName, response.getEquipmentName());
            queryWrapper1.last(" and DATE_FORMAT(time, '%Y-%m-%d') = '" + response.getDate() + "'");
            List<Current> currentList = currentDao.selectList(queryWrapper1);
            // 如果温度表数据少于电流表（电流表数据多） - 将电流表数据只保留部分数据插入
            // 温度表10条数据，电流表13条数据，只取电流表前10条数据插入到温度表中
            if (response.getNum() >= temperatureCount) {
                for (int i = 0; i < temperatureCount; i++) {
                    Temperature temperature = temperatureList.get(i);
                    temperature.setLoadCurrent(currentList.get(i).getCurrent());

                    insertIntoPredictFeature(temperature);
                }
            } else {
                // 如果温度表数据多于电流表（电流表数据少） - 循环数据插入
                int c_idx = 0;
                for (int i = 0; i < temperatureCount; i++) {
                    c_idx = c_idx % currentList.size();
                    Temperature temperature = temperatureList.get(i);
                    temperature.setLoadCurrent(currentList.get(c_idx).getCurrent());
                    c_idx++;

                    insertIntoPredictFeature(temperature);
                }
            }

            // 把电流表插入温度表中，从电流表中取与温度表时间最接近的两条记录
            // 如果其中最接近的一条在10分钟以内，就用这一条数据填充
            // 如果两个时间都在十分钟开外，则把这两个时间的电流取平均值填充
            /*for (int i = 0; i < temperatureCount; i++) {
                Temperature temperature = temperatureList.get(i);
                // 取最近的两条电流数据
                LambdaQueryWrapper<Current> queryWrapper1 = new LambdaQueryWrapper<>();
                queryWrapper1.eq(Current::getEquipmentName, response.getEquipmentName());
                queryWrapper1.last(" and DATE_FORMAT(time, '%Y-%m-%d') = '" + response.getDate() + "' order by (UNIX_TIMESTAMP(time) - UNIX_TIMESTAMP('" + temperature.getCheckTime() + "')) limit 2");
                List<Current> currentList = currentDao.selectList(queryWrapper1);
                double avg = 0.0;
                for (Current c : currentList) {
                    avg += Double.parseDouble(c.getCurrent()) / currentList.size();
                }
                temperature.setLoadCurrent(String.valueOf(avg));
            }*/
        }
        return true;
    }

    /**
     * 构造完整temperature对象，向特征表插入数据
     *
     * @param temperature
     * @return
     */
    private boolean insertIntoPredictFeature(Temperature temperature) {
        // 构造feature对象，检查是否已经导入过，没有导入过就导入
        LambdaQueryWrapper<PredictFeature> queryWrapper2 = new LambdaQueryWrapper<>();
        queryWrapper2.eq(PredictFeature::getEquipmentName, temperature.getEquipmentName());
        queryWrapper2.eq(PredictFeature::getCheckTime, temperature.getCheckTime());
        List<PredictFeature> featureList = predictFeatureDao.selectList(queryWrapper2);
        if (CollUtil.isEmpty(featureList)) { // 没有导入过数据
            PredictFeature predictFeature = new PredictFeature();
            BeanUtils.copyProperties(temperature, predictFeature);
            // 如果数据列有空值，跳过
            if (!predictFeature.checkValid()) {
                LOGGER.warn("feature has empty column, skip it. feature: {}", predictFeature);
                return false;
            }
            return predictFeatureDao.insert(predictFeature) > 0;
        } else {
            LOGGER.warn("特征表已经导入过数据");
        }
        return false;
    }

    @Override
    public List predictModel(String usingModel) {
        List resultList = new LinkedList<>();
        // 判断输入的模型类型是否支持
        if (!AVAILABLE_MODELS_MAP.containsKey(usingModel)) {
            throw new PowerException("您选择了不支持的模型类型，请重新选择[ LSTM或GRU ]！");
        }
        LOGGER.info("当前选择的模型类型是：{}", usingModel);

        //生成最新的数据库feature表
        mergePredictFeature();

        boolean genRst = generateCvsFileFromDB();
        if (!genRst) {
            throw new PowerException("生成预测数据集predictfeature.csv时发生错误！");
        }


        String python = powerConfig.getPython();
        String scriptPath = powerConfig.getModel_script_path() + File.separator + "PredictModel.py";
        if (!FileUtil.exist(scriptPath)) {
            throw new PowerException("在" + powerConfig.getModel_script_path() + "路径下不存在模型预测脚本PredictModel.py！请将脚本上传到该路径下");
        }
        String inputDataSet = powerConfig.getInput_csv_path() + File.separator + "predictfeature.csv";
        if (!FileUtil.exist(inputDataSet)) {
            throw new PowerException("预测数据集predictfeature.csv不存在");
        }

        String usingModelPath = powerConfig.getModel_file_path() + File.separator + usingModel + ".h5";
        String outputDataSet = "";
        // 偏差值路径
        String outputDeviation = "";
        if ("GRU".equals(usingModel)) {
            outputDataSet = powerConfig.getOutput_csv_path() + File.separator + "feature_output_GRU.csv";
            outputDeviation = powerConfig.getOutput_csv_path() + File.separator + "feature_output_deviation_GRU.csv";
        } else if ("LSTM".equals(usingModel)) {
            outputDataSet = powerConfig.getOutput_csv_path() + File.separator + "feature_output_LSTM.csv";
            outputDeviation = powerConfig.getOutput_csv_path() + File.separator + "feature_output_deviation_LSTM.csv";
        }
        try {
            FileUtil.createFile(outputDataSet);
            FileUtil.createFile(outputDeviation);
        } catch (Exception e) {
            throw new PowerException("创建模型预测结果文件失败！" + outputDataSet + ", " + outputDeviation);
        }
        final String finalOutputDataSet = outputDataSet;
        final String finalOutputDeviation = outputDeviation;
        //boolean predictRst =  PyCmdUtil.predictModel(scriptPath, inputDataSet, usingModelPath, outputDataSet);

        // 使用Future调用模型预测
        Future<Boolean> predictFuture = executorService.submit(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return PyCmdUtil.predictModel(python, scriptPath, inputDataSet, usingModelPath, finalOutputDataSet, finalOutputDeviation);
            }
        });
        try {
            boolean rst = predictFuture.get(360, TimeUnit.SECONDS);
            if (rst) {
                List<String> dataList = new LinkedList<>();
                List<String> timeList = new LinkedList<>();
                List<String> deviationList = new LinkedList<>();
                // 修改
                // 真实值
                List<String> realDataList = new LinkedList<>();

                // 加载预测结果集并赋值到结果中
                // dataList
                List<CsvRow> csvRows = CsvUtils.read(finalOutputDataSet);

                //取最后30条数据显式
                for (int i = csvRows.size() - 30; i < csvRows.size(); i++) {
                    dataList.add(csvRows.get(i).get(1)); // 只取每行的第一列
                    // 修改
                    realDataList.add(csvRows.get(i).get(7)); // 取第7列
                    timeList.add((csvRows.get(i).get(8)));//取第8列

                }

                //追加预测的5个值
                for (int i = 2; i < 7; ++i) {
                    dataList.add(csvRows.get(csvRows.size() - 1).get(i));
                }
//                for(int i = 0; i<5; ++i){
//                    realDataList.add(csvRows.get(csvRows.size()-1).get(7));
//                }
                // timeList 计算对应每个记录的时间
                String lasTime = predictFeatureDao.getLastTimeInFeature();
                System.out.println(csvRows.size());
                for (int i = 0; i < 5; i++) {
                    String time = DateUtil.dateToStr(DateUtil.addSecond(DateUtil.strToDate(lasTime, Constants.DATE_FORMAT), 60 * 60), Constants.DATE_FORMAT);
                    timeList.add(time);
                    lasTime = time;
                }
                // deviationList
                List<CsvRow> deviationRows = CsvUtils.read(finalOutputDeviation);
                if (deviationRows.size() > 0) {
                    for (int i = 1; i <= 2; i++) {
                        double d = Double.parseDouble(deviationRows.get(1).get(i));
                        deviationList.add(String.format("%.3f", d)); // 只取每行的第一列
                    }
                }

                resultList.add(dataList);
                resultList.add(timeList);
                resultList.add(deviationList);
                // 修改
                resultList.add(realDataList);
                return resultList;
            }
        } catch (InterruptedException e) {
            throw new PowerException("调用模型预测脚本时发生错误！errMsg: " + e.getMessage());
        } catch (ExecutionException e) {
            throw new PowerException("调用模型预测脚本时发生错误！errMsg: " + e.getMessage());
        } catch (TimeoutException e) {
            throw new PowerException("调用模型预测脚本时发生错误！errMsg: " + e.getMessage());
        }
        // 读取预测结果
        return resultList;
    }

    /**
     * 读取mysql数据库predictfeature表，转化为csv文件
     */
    public synchronized boolean generateCvsFileFromDB() {
        // 表头
        // select column_comment from information_schema.COLUMNS where table_name = 'feature' and table_schema = 'power' and column_key != 'PRI';
        List<String> headers = predictFeatureDao.getColumnCommnet();
        PredictFeature featureHeader = new PredictFeature();
        featureHeader.setCheckTime(headers.get(0));
        featureHeader.setEquipmentName(headers.get(1));
        featureHeader.setEquipmentTemperature(headers.get(2));
        featureHeader.setIntervalUnit(headers.get(3));
        featureHeader.setPhase(headers.get(4));
        featureHeader.setEnvTemperature(headers.get(5));
        featureHeader.setHumidity(headers.get(6));
        featureHeader.setWindSpeed(headers.get(7));
        featureHeader.setWindDirection(headers.get(8));
        featureHeader.setLoadCurrent(headers.get(9));

        // 数据
        LambdaQueryWrapper<PredictFeature> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(PredictFeature::getCheckTime, PredictFeature::getEquipmentName, PredictFeature::getEquipmentTemperature, PredictFeature::getIntervalUnit,
                PredictFeature::getPhase, PredictFeature::getEnvTemperature, PredictFeature::getHumidity, PredictFeature::getWindSpeed, PredictFeature::getWindDirection,
                PredictFeature::getLoadCurrent);
        // queryWrapper.last(" limit 10000");
        List<PredictFeature> featureList = predictFeatureDao.selectList(queryWrapper);

        // 把表头数据加入最终的表格
        featureList.add(0, featureHeader);


        // list 转换为 String[]
        List<String[]> featureListStr = new LinkedList<>();
        for (PredictFeature feature : featureList) {
            String[] featuerStr = new String[10];
            featuerStr[0] = feature.getCheckTime();
            featuerStr[1] = feature.getEquipmentName();
            featuerStr[2] = feature.getEquipmentTemperature();
            featuerStr[3] = feature.getIntervalUnit();
            featuerStr[4] = feature.getPhase();
            featuerStr[5] = feature.getEnvTemperature();
            featuerStr[6] = feature.getHumidity();
            featuerStr[7] = feature.getWindSpeed();
            featuerStr[8] = feature.getWindDirection();
            featuerStr[9] = feature.getLoadCurrent();
            featureListStr.add(featuerStr);
        }

        // 调用工具类生成csv文件
        Future<Boolean> createCsvFuture = executorService.submit(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                return CsvUtils.createCsvFile(featureListStr, powerConfig.getInput_csv_path(), "predictfeature.csv");
            }
        });
        try {
            boolean rst = createCsvFuture.get(30, TimeUnit.SECONDS);
            if (rst) {
                return true;
            }
        } catch (InterruptedException e) {
            throw new PowerException("生成csv文件超时或者失败！errMsg: " + e.getMessage());
        } catch (ExecutionException e) {
            throw new PowerException("生成csv文件超时或者失败！errMsg: " + e.getMessage());
        } catch (TimeoutException e) {
            throw new PowerException("生成csv文件超时或者失败！errMsg: " + e.getMessage());
        }
        return false;
    }


}
